﻿// Connect.h: CConnect 的声明

#pragma once
#include "resource.h"       // 主符号

#include <gdiplus.h>
#include <string>
#include "NativePPTAddin_i.h"

#pragma comment(lib, "gdiplus.lib")

#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Windows CE 平台(如不提供完全 DCOM 支持的 Windows Mobile 平台)上无法正确支持单线程 COM 对象。定义 _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA 可强制 ATL 支持创建单线程 COM 对象实现并允许使用其单线程 COM 对象实现。rgs 文件中的线程模型已被设置为“Free”，原因是该模型是非 DCOM Windows CE 平台支持的唯一线程模型。"
#endif

using namespace ATL;

typedef IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &__uuidof(__AddInDesignerObjects), /* wMajor = */ 1>
IDTImpl;

typedef IDispatchImpl<IRibbonExtensibility, &__uuidof(IRibbonExtensibility), &__uuidof(__Office), /* wMajor = */ 2, /* wMinor = */ 5>
RibbonImpl;

typedef IDispatchImpl<IRibbonCallback, &__uuidof(IRibbonCallback)>
CallbackImpl;

namespace {
	HRESULT HrGetResource(int nId,
		LPCTSTR lpType,
		LPVOID* ppvResourceData,
		DWORD* pdwSizeInBytes)
	{
		HMODULE hModule = _AtlBaseModule.GetModuleInstance();
		if (!hModule)
			return E_UNEXPECTED;
		HRSRC hRsrc = FindResource(hModule, MAKEINTRESOURCE(nId), lpType);
		if (!hRsrc)
			return HRESULT_FROM_WIN32(GetLastError());
		HGLOBAL hGlobal = LoadResource(hModule, hRsrc);
		if (!hGlobal)
			return HRESULT_FROM_WIN32(GetLastError());
		*pdwSizeInBytes = SizeofResource(hModule, hRsrc);
		*ppvResourceData = LockResource(hGlobal);
		return S_OK;
	}

	BSTR GetXMLResource(int nId)
	{
		LPVOID pResourceData = NULL;
		DWORD dwSizeInBytes = 0;
		HRESULT hr = HrGetResource(nId, TEXT("XML"),
			&pResourceData, &dwSizeInBytes);
		if (FAILED(hr))
			return NULL;
		// Assumes that the data is not stored in Unicode.
		CComBSTR cbstr(dwSizeInBytes, reinterpret_cast<LPCSTR>(pResourceData));
		return cbstr.Detach();
	}

	SAFEARRAY* GetOFSResource(int nId)
	{
		LPVOID pResourceData = NULL;
		DWORD dwSizeInBytes = 0;
		if (FAILED(HrGetResource(nId, TEXT("OFS"),
			&pResourceData, &dwSizeInBytes)))
			return NULL;
		SAFEARRAY* psa;
		SAFEARRAYBOUND dim = { dwSizeInBytes, 0 };
		psa = SafeArrayCreate(VT_UI1, 1, &dim);
		if (psa == NULL)
			return NULL;
		BYTE* pSafeArrayData;
		SafeArrayAccessData(psa, (void**)&pSafeArrayData);
		memcpy((void*)pSafeArrayData, pResourceData, dwSizeInBytes);
		SafeArrayUnaccessData(psa);
		return psa;
	}
	HRESULT HrGetImageFromResource(int nId, LPCTSTR lpType, IPictureDisp** ppdispImage)
	{
		LPVOID pResourceData = NULL;
		DWORD len = 0;
		HRESULT hr = HrGetResource(nId, lpType, &pResourceData, &len);
		if (FAILED(hr)) {
			return E_UNEXPECTED;
		}
		IStream* pStream = nullptr;
		HGLOBAL hGlobal = nullptr;
		// copy image bytes into a real hglobal memory handle
		hGlobal = ::GlobalAlloc(GHND, len);
		if (hGlobal) {
			void* pBuffer = ::GlobalLock(hGlobal);
			if (pBuffer) {
				memcpy(pBuffer, reinterpret_cast<BYTE*>(pResourceData), len);
				HRESULT hr = CreateStreamOnHGlobal(hGlobal, TRUE, &pStream);
				if (SUCCEEDED(hr)) {
					// pStream now owns the global handle and will invoke GlobalFree on release
					hGlobal = nullptr;

					PICTDESC pic;
					memset(&pic, 0, sizeof pic);
					Gdiplus::Bitmap* png = Gdiplus::Bitmap::FromStream(pStream);
					HBITMAP hMap = NULL;
					png->GetHBITMAP(Gdiplus::Color(), &hMap);
					pic.picType = PICTYPE_BITMAP;
					pic.bmp.hbitmap = hMap;

					OleCreatePictureIndirect(&pic, IID_IPictureDisp, true, (LPVOID*)ppdispImage);
				}
			}
		}
		if (pStream) {
			pStream->Release();
			pStream = nullptr;
		}
		if (hGlobal) {
			GlobalFree(hGlobal);
			hGlobal = nullptr;
		}
		return S_OK;
	}
} // End of anonymous namespace

// CConnect

class ATL_NO_VTABLE CConnect :
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClass<CConnect, &CLSID_Connect>,
	public IDispatchImpl<IConnect, &IID_IConnect, &LIBID_NativePPTAddinLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
	//public IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &LIBID_AddInDesignerObjects, /* wMajor = */ 1, /* wMinor = */ 0>
	public IDTImpl,
	public RibbonImpl,
	public CallbackImpl
{
public:
	CConnect()
	{
	}

	DECLARE_REGISTRY_RESOURCEID(106)

	BEGIN_COM_MAP(CConnect)
		COM_INTERFACE_ENTRY2(IDispatch, IRibbonCallback)
		COM_INTERFACE_ENTRY(IConnect)
		COM_INTERFACE_ENTRY2(IDispatch, _IDTExtensibility2)
		COM_INTERFACE_ENTRY(_IDTExtensibility2)
		COM_INTERFACE_ENTRY(IRibbonExtensibility)
		COM_INTERFACE_ENTRY(IRibbonCallback)
	END_COM_MAP()

	DECLARE_PROTECT_FINAL_CONSTRUCT()

	HRESULT FinalConstruct()
	{
		return S_OK;
	}

	void FinalRelease()
	{
	}

private:
	CComPtr<IDispatch> m_spApplication;

public:

	// _IDTExtensibility2 Methods
public:
	STDMETHOD(OnConnection)(LPDISPATCH Application, ext_ConnectMode ConnectMode, LPDISPATCH AddInInst, SAFEARRAY** custom)
	{
		m_spApplication = Application;
		::MessageBoxW(NULL, L"OnConnection", L"Native PPT Addin", MB_OK);
		return S_OK;
	}

	STDMETHOD(OnDisconnection)(ext_DisconnectMode RemoveMode, SAFEARRAY** custom)
	{
		//return E_NOTIMPL;
		return S_OK;
	}

	STDMETHOD(OnAddInsUpdate)(SAFEARRAY** custom)
	{
		//return E_NOTIMPL;
		return S_OK;
	}

	STDMETHOD(OnStartupComplete)(SAFEARRAY** custom)
	{
		//return E_NOTIMPL;
		return S_OK;
	}

	STDMETHOD(OnBeginShutdown)(SAFEARRAY** custom)
	{
		//return E_NOTIMPL;
		return S_OK;
	}

	STDMETHOD(GetCustomUI)(BSTR RibbonID, BSTR* RibbonXml)
	{
		if (!RibbonXml)
			return E_POINTER;
		//*RibbonXml = CComBSTR("XML GOES HERE");
		*RibbonXml = GetXMLResource(IDR_XML1);
		return S_OK;
	}
	STDMETHODIMP_(HRESULT __stdcall) CConnect::ButtonClicked(IDispatch* control)
	{
		CComQIPtr<IRibbonControl> ribbonCtl(control);
		CComBSTR idStr;
		WCHAR msg[64];
		if (ribbonCtl->get_Id(&idStr) != S_OK)
			return S_FALSE;
		if (idStr == OLESTR("loginButton")) {
			swprintf_s(msg, L"I am loginButton");
		}
		else if (idStr == OLESTR("uploadButton")) {
			swprintf_s(msg, L"I am uploadButton");
		}
		else if (idStr == OLESTR("countTextBoxButton")) {
			// 推荐：在类中添加成员变量 CComPtr<IDispatch> m_spApplication; 并在 OnConnection 里赋值
			if (!m_spApplication) {
				swprintf_s(msg, L"无法获取PPT应用对象");
				MessageBoxW(NULL, msg, L"NativePPTAddin", MB_OK);
				return S_FALSE;
			}
			CComQIPtr<PowerPoint::_Application> spApp(m_spApplication);
			if (!spApp) {
				swprintf_s(msg, L"无法转换为PPT应用对象");
				MessageBoxW(NULL, msg, L"NativePPTAddin", MB_OK);
				return S_FALSE;
			}
			// 获取当前演示文稿
			CComPtr<PowerPoint::_Presentation> spPres;
			HRESULT hr = spApp->get_ActivePresentation(&spPres);
			if (FAILED(hr) || !spPres) {
				swprintf_s(msg, L"未打开演示文稿");
				MessageBoxW(NULL, msg, L"NativePPTAddin", MB_OK);
				return S_FALSE;
			}
			// 遍历所有幻灯片和形状，统计非空文本框
			long textBoxCount = 0;
			CComPtr<PowerPoint::Slides> spSlides;
			spPres->get_Slides(&spSlides);
			if (spSlides) {
				long slideCount = 0;
				spSlides->get_Count(&slideCount);
				for (long i = 1; i <= slideCount; i++) {
					CComPtr<PowerPoint::_Slide> spSlide;
					hr = spSlides->Item(CComVariant(i), &spSlide);
					if (FAILED(hr)) continue;
					CComPtr<PowerPoint::Shapes> spShapes;
					hr = spSlide->get_Shapes(&spShapes);
					if (FAILED(hr)) continue;
					int shapeCount;
					spShapes->get_Count(&shapeCount);
					for (long j = 1; j <= shapeCount; j++) {
						CComPtr<PowerPoint::Shape> spShape;
						hr = spShapes->Item(CComVariant(j), &spShape);
						if (FAILED(hr)) continue;
						Office::MsoTriState hasTextFrame;
						hr = spShape->get_HasTextFrame(&hasTextFrame);
						if (FAILED(hr) || hasTextFrame == VARIANT_FALSE) continue;
						CComPtr<PowerPoint::TextFrame> spTextFrame;
						hr = spShape->get_TextFrame(&spTextFrame);
						if (FAILED(hr)) continue;
						CComPtr<PowerPoint::TextRange> spTextRange;
						hr = spTextFrame->get_TextRange(&spTextRange);
						if (FAILED(hr)) continue;
						CComBSTR text;
						hr = spTextRange->get_Text(&text);
						if (FAILED(hr)) continue;
						// 检查文本是否非空（去除首尾空白）
						if (text.Length() > 0) {
							std::wstring textStr = (BSTR)text;
							textStr.erase(0, textStr.find_first_not_of(L" \t\r\n"));
							textStr.erase(textStr.find_last_not_of(L" \t\r\n") + 1);
							if (!textStr.empty()) {
								textBoxCount++;
							}
						}
					}
				}
			}
			swprintf_s(msg, L"非空文本框数量: %ld", textBoxCount);
		}
		else if (idStr == OLESTR("insertSlideButton"))
		{
			// 获取当前演示文稿
			CComQIPtr<PowerPoint::_Application> spApp(m_spApplication);
			CComPtr<PowerPoint::_Presentation> spPresentation;

			HRESULT hr = spApp->get_ActivePresentation(&spPresentation);
			if (FAILED(hr))
				return hr;

			// 获取幻灯片集合
			CComPtr<PowerPoint::Slides> spSlides;
			hr = spPresentation->get_Slides(&spSlides);
			if (FAILED(hr))
				return hr;

			long slideCount = 0;
			spSlides->get_Count(&slideCount);

			// 添加新的幻灯片
			CComPtr<PowerPoint::_Slide> spNewSlide;
			hr = spSlides->Add(slideCount + 1, ppLayoutText, &spNewSlide);
			if (FAILED(hr))
				return hr;

			// 设置背景色为主题色着色 1
			CComPtr<PowerPoint::ShapeRange> spBgShapeRange;
			hr = spNewSlide->get_Background(&spBgShapeRange);
			if (SUCCEEDED(hr) && spBgShapeRange) {
				CComPtr<PowerPoint::FillFormat> spFill;
				hr = spBgShapeRange->get_Fill(&spFill);
				if (SUCCEEDED(hr) && spFill) {
					// 获取 ForeColor
					CComPtr<PowerPoint::ColorFormat> spColor;
					hr = spFill->get_ForeColor(&spColor);
					if (SUCCEEDED(hr) && spColor) {
						// 设置主题色
						spColor->put_SchemeColor(ppAccent1);
						//spColor->put_ObjectThemeColor(msoThemeAccent1);
					}
				}
			}
			swprintf_s(msg, L"已插入新幻灯片并设置主题色");
		}
		MessageBoxW(NULL, msg, L"NativePPTAddin", MB_OK);
		return S_OK;
	}

	STDMETHODIMP_(HRESULT __stdcall) CConnect::GetLabel(IDispatch* control, BSTR* returnedVal)
	{
		CComQIPtr<IRibbonControl> ribbonCtl(control);
		CComBSTR idStr;
		if (ribbonCtl->get_Id(&idStr) != S_OK)
			return S_FALSE;
		CComBSTR ret;
		if (idStr == OLESTR("loginButton")) {
			ret = OLESTR("登录");
		}
		else if (idStr == OLESTR("uploadButton")) {
			ret = OLESTR("上传");
		}
		else if (idStr == OLESTR("countTextBoxButton"))
		{
			ret = OLESTR("计算");
		}
		else if (idStr == OLESTR("insertSlideButton"))
		{
			ret = OLESTR("插入");
		}
		*returnedVal = ret.Detach();
		return S_OK;
	}

	STDMETHODIMP_(HRESULT __stdcall) CConnect::GetVisible(IDispatch* control, VARIANT_BOOL* returnedVal)
	{
		CComQIPtr<IRibbonControl> ribbonCtl(control);
		CComBSTR idStr;
		if (ribbonCtl->get_Id(&idStr) != S_OK)
			return S_FALSE;
		if (idStr == OLESTR("loginButton")) {
			*returnedVal = VARIANT_TRUE;
		}
		else if (idStr == OLESTR("uploadButton")) {
			*returnedVal = VARIANT_TRUE;
		}
		else if (idStr == OLESTR("countTextBoxButton")) {
			*returnedVal = VARIANT_TRUE;
		}
		else if (idStr == OLESTR("insertSlideButton")) {
			*returnedVal = VARIANT_TRUE;
		}
		return S_OK;
	}

	STDMETHODIMP_(HRESULT __stdcall) CConnect::CustomUILoadImage(BSTR* imageId, IPictureDisp** returnedVal)
	{
		return HrGetImageFromResource(_wtoi(*imageId), TEXT("PNG"), returnedVal);
	}

	STDMETHODIMP_(HRESULT __stdcall) CConnect::GetImage(IDispatch* control, IPictureDisp** returnedVal)
	{
		CComQIPtr<IRibbonControl> ribbonCtl(control);
		CComBSTR idStr;
		if (ribbonCtl->get_Id(&idStr) != S_OK)
			return S_FALSE;
		if (idStr == OLESTR("loginButton")) {
			// do nothing
			// 登录按钮使用image属性定义
		}
		else if (idStr == OLESTR("uploadButton")) {
			return HrGetImageFromResource(IDB_PNG2, TEXT("PNG"), returnedVal);
		}
		else if (idStr == OLESTR("countTextBoxButton")) {
			return HrGetImageFromResource(IDB_PNG3, TEXT("PNG"), returnedVal);
		}
		else if (idStr == OLESTR("insertSlideButton")) {
			return HrGetImageFromResource(IDB_PNG4, TEXT("PNG"), returnedVal);
		}
		return S_OK;
	}
};

OBJECT_ENTRY_AUTO(__uuidof(Connect), CConnect)
